// -----------------------------
// Author: 		Dhaenens Gert	
// Creation: 	10/16/2014 6:44:26 PM				
// -----------------------------

#include "ForwardPass.h"

#include "Application.h"
#include "RenderManager.h"

#include "Material.h"
#include "ForwardMaterial.h"
#include "Scene.h"
#include "Camera.h"

#include "LightActor.h"
#include "MeshActor.h"
#include "Mesh.h"

ForwardPass::ForwardPass(RenderManager* lpRenderManager, const RenderTargets& targets)
	: RenderPass(lpRenderManager, "Forward", targets)
{
}

ForwardPass::~ForwardPass()
{
	NullRelease(&m_lpBlendState);
}

void ForwardPass::Init(Application* lpApp)
{
	m_lpBlendState = IBlendState::Create(lpApp->GetRenderer()->GetDevice());
}
void ForwardPass::Apply(Application* lpApp, Scene* lpScene)
{
	IRenderDevice* lpDevice = m_lpRenderManager->GetDevice();

	// Bind the Output Targets
	lpDevice->SetRenderTarget(m_Targets.RawRenderTarget3D, m_Targets.DepthStencil);
	// Bind the Blend State
	lpDevice->SetBlendState(m_lpBlendState);

	// Get the Meshes to be Renderered
	vector<MeshActor*> meshActors = lpScene->GetActorsWithFlagsAs<MeshActor>(Actor::Geometry);
	// Get the Lights
	vector<LightActor*> lightActors = lpScene->GetActorsWithFlagsAs<LightActor>(Actor::Light);

	// Get the Camera Properties
	Matrix view = lpScene->GetCamera()->GetView();
	Matrix projection = lpScene->GetCamera()->GetProjection();

	// Loop over the Lights
	for (LightActor* lpLight : lightActors)
	{
		// Render the Meshes
		Mesh* lpLastMesh = nullptr;
		Material* lpLastMaterial = nullptr;
		for (MeshActor* lpActor : meshActors)
		{
			// Get the Mesh & Material
			Mesh* lpMesh = lpActor->GetMesh();
			Material* lpMaterial = lpActor->GetMaterial();

			// Make sure that we have
			// a mesh & material
			if (!lpMaterial || !lpMesh)
				continue;

			// Make sure the the Material is
			// a Forward Type material, if not
			// Skip this actor since it was already rendererd
			// in the Deferred pass
			if (lpMaterial->GetRenderType() != Material::RenderType::Forward)
				continue;

			// If it is a different mesh than before
			// Bind the vertex & index buffer
			if (lpMesh != lpLastMesh)
			{
				lpDevice->SetVertexBuffer(0, lpMesh->GetVertexBuffer());
				lpDevice->SetIndexBuffer(lpMesh->GetIndexBuffer());
			}

			// Update the Material Matrices
			lpMaterial->m_World = lpActor->GetWorld();
			lpMaterial->m_View = view;
			lpMaterial->m_Projection = projection;
			lpMaterial->UpdateMatrices(lpApp);

			// If it is a differen material than before
			// Update the material data
			// And bind itself
			if (lpMaterial != lpLastMaterial)
			{
				ForwardMaterial* lpForwardMaterial = static_cast<ForwardMaterial*>(lpMaterial);
				lpForwardMaterial->m_LightPosition = lpLight->GetPosition();
				lpForwardMaterial->m_LightDirection = lpLight->GetForward();
				lpForwardMaterial->m_LightIntensity = lpLight->GetIntensity();
				lpForwardMaterial->m_LightRange = lpLight->GetRange();
				lpForwardMaterial->m_LightType = lpLight->GetLightType();

				lpMaterial->UpdateData(lpApp);
				lpMaterial->Apply(lpApp);

				RenderState eState = m_lpRenderManager->GetRenderState();
				if (eState != RenderState::Wireframe)
				{
					if (eState == RenderState::Default && lpMaterial->IsTwoSided())
						m_lpRenderManager->SetRenderState(RenderState::DefaultNoCull, false);
					else if (eState == RenderState::DefaultNoCull && !lpMaterial->IsTwoSided())
						m_lpRenderManager->SetRenderState(RenderState::Default, false);
				}
			}

			// Draw the Mesh
			lpDevice->DrawIndexed(lpMesh->GetIndexBuffer()->GetCount(), 0, 0);

			// Save the Mesh & Material
			lpLastMesh = lpMesh;
			lpLastMaterial = lpMaterial;
		}
	}

	// Unbind the Output Target
	lpDevice->SetRenderTarget(nullptr, nullptr);
	// Unbind the Blend State
	lpDevice->SetBlendState(nullptr);
}